package com.example.yx.demo21;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

public class A21 {
    /*
    目标:解析控制器方法的参数值

    常见的参数解析器：
        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@6e521c1e    //请求参数解析器
        org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@224b4d61
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@5d5d9e5
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@303e3593
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@4ef27d66
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@362a019c
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1d9bec4d
        org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5c48c0c0
        org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@10c8f62
        org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@674c583e
        org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@25f7391e
        org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@3f23a3a0
        org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@5ab14cb9
        org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@5fb97279
        org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@439a8f59
        org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@61861a29
        org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@31024624
        org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@25bcd0c7
        org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@32cb636e
        org.springframework.web.method.annotation.ModelMethodProcessor@63cd604c
        org.springframework.web.method.annotation.MapMethodProcessor@40dd3977
        org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@3a4e343
        org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@6a1d204a
        org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@62dae245
        org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@4b6579e8
        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@6fff253c
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@6c6357f9
     */

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        //准备测试Request
        HttpServletRequest mock = mockRequest();

        //要点1，控制器方法被封装成 HandlerMethod
        HandlerMethod method = new HandlerMethod(new Controller(),
                Controller.class.getMethod
                        ("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

        //要点2，准备对象绑定和类型转化

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,null);

        //要点3，准备 ModelAndViewContainer 用来存储中间Model结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        //要点4，解析每个参数值
        for (MethodParameter parameter : method.getMethodParameters()) {
            //  通过ApplicationContext容器的解析${}                                                                                                  false表示必须要有@RequestParam注解
            RequestParamMethodArgumentResolver resolver = new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false);

            //多个解析器的组合
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                    resolver,
                    new PathVariableMethodArgumentResolver(),
                    new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                    new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                    new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                    new ServletRequestMethodArgumentResolver(),
                    new ServletModelAttributeMethodProcessor(false),//必须有 @ModelAttribute
                    new ServletModelAttributeMethodProcessor(true),//省略 @ModelAttribute,优先走上面的
                    new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
            );
            String collect = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
            if (composite.supportsParameter(parameter)) {
                //支持此参数,factory用于将字符串解析成与参数匹配指定类型
                Object argument = composite.resolveArgument(parameter, container, new ServletWebRequest(mock), factory);
//                System.out.println(argument.getClass());
                System.out.println("[" + parameter.getParameterIndex()+"]" + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + " -> " + argument);
//                System.out.println("模型数据为: " + container.getModel());
            } else {
                System.out.println("[" + parameter.getParameterIndex()+"]" + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            }

//            System.out.println(collect);
//            System.out.println("[" + parameter.getParameterIndex()+"]" + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
        }
    }

    private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1","zhangsan");
        request.setParameter("name2","lisi");
        request.addPart(new MockPart("file","abc","hello".getBytes(StandardCharsets.UTF_8)));
        Map<String,String> uriTemplateVariables = new AntPathMatcher().extractUriTemplateVariables("/test/{id}","/test/123");
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,uriTemplateVariables);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token","123456"));
        request.setParameter("name","张三");
        request.setParameter("age","18");
//        request.setContent("""
//                {
//                    "name":"李四",
//                    "age":20
//                }
//                """.getBytes(StandardCharsets.UTF_8));
        return new StandardServletMultipartResolver().resolveMultipart(request);
    }

    static class Controller {
        public void test(
                @RequestParam("name1") String name1,//解析：传过来的参数名是否是@RequestParam()里的参数，是就将它放入String name1里的name1
                String name2,                       //解析：传过来的参数名是否是name2，是就放入，这两种都是同一种解析器
                @RequestParam("age") int age,       //由于传过来的都是字符串，需要转换成int
                @RequestParam(name = "home",defaultValue = "${JAVA_HOME}") String home1,//可以使用${}来获取非请求中的数据，Spring中获取
                @RequestParam("file")MultipartFile file,        //获取上传文件
                @PathVariable("id") int id,                     //获取路径参数：如路径: /test/123 ，使用时通过/test/{id}来得到这个值
                @RequestHeader("Content-Type") String header,   //解析请求头
                @CookieValue("token") String token,             //解析Cookie
                @Value(("${JAVA_HOME}")) String home2,          //可以使用${}来获取非请求中的数据，Spring中获取
                HttpServletRequest request,                     //对于request、response、session..都由各自的解析器解析
                @ModelAttribute User user1,                     //参数为name=xx&age=18，会解析成对象的属性之类的并封装
                User user2,                                     //参数为name=xx&age=18，会解析成对象的属性之类的并封装
                @RequestBody User user3                         //要从请求体里的数据（json），然后封装成这个对象
                ) {

        }
    }

    static class User {
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }

        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
}
